﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SBToolkit.Core
{
    /// <summary>
    /// Provides simplified access to the meta information.
    /// </summary>
    public static class Reflector
    {
        /// <summary>
        /// Binding flags to use for reflection.
        /// </summary>
        private const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Public |
                                           BindingFlags.Static | BindingFlags.Instance;

        /// <summary>
        /// Gets the <see cref="MethodInfo"/> for the method that is called in the provided <paramref name="expression"/>.
        /// </summary>
        /// <typeparam name="T">The type of the class.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <returns>Method info.</returns>
        /// <exception cref="ArgumentException">The provided expression is not a method call.</exception>
        public static MethodInfo GetMethod<T>(Expression<Action<T>> expression)
        {
            var methodCall = expression.Body as MethodCallExpression;
            if (methodCall == null)
            {
                throw new ArgumentException("Expected method call");
            }

            return methodCall.Method;
        }

        /// <summary>
        /// Gets the name of the method that is called in the provided <paramref name="expression"/>.
        /// </summary>
        /// <typeparam name="T">The type of the class.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <returns>Method name.</returns>
        public static string GetMethodName<T>(Expression<Action<T>> expression)
        {
            return GetMethod(expression).Name;
        }

        /// <summary>
        /// Gets the <see cref="PropertyInfo"/> for the property that is specified in the provided <paramref name="expression"/>.
        /// </summary>
        /// <typeparam name="T">The type of the class.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <returns>Property info.</returns>
        /// <exception cref="ArgumentException">The provided expression is not a property.</exception>
        public static PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
        {
            MemberExpression member;
            var unary = expression.Body as UnaryExpression;
            if (unary != null)
            {
                member = unary.Operand as MemberExpression;
            }
            else
            {
                member = expression.Body as MemberExpression;
            }

            if (member == null || !(member.Member is PropertyInfo))
            {
                throw new ArgumentException("Expected property expression");
            }

            return member.Member as PropertyInfo;
        }

        /// <summary>
        /// Gets the name of the property that is specified in the provided <paramref name="expression"/>.
        /// </summary>
        /// <typeparam name="T">The type of the class.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <returns>Property name.</returns>
        public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
        {
            return GetProperty(expression).Name;
        }

        /// <summary>
        /// Determines whether specified type is attributed as <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The type of the attribute.</typeparam>
        /// <param name="obj">The object to check for specified attribute.</param>
        /// <returns><c>True</c> if the object is attributed as <typeparamref name="T"/>; otherwise, <c>false</c>.</returns>
        public static bool IsAttributedAs<T>(this object obj) where T : Attribute
        {
            return obj.GetType().GetCustomAttributes(false).Where(x => x is T).Any();
        }

        /// <summary>
        /// Creates new dynamic object of the specified type.
        /// </summary>
        /// <param name="assembly">The assembly specified type is in.</param>
        /// <param name="typeName">The name of the type.</param>
        /// <param name="ctorArgs">Constructor arguments to use for creation.</param>
        /// <returns>Dynamic object of the specified type.</returns>
        public static dynamic New(this Assembly assembly, string typeName, params object[] ctorArgs)
        {
            var type = assembly.GetTypes().First(item => item.Name == typeName);
            var ctorArgTypes = ctorArgs.Select(arg => arg.GetType()).ToArray();
            var ctor = type.GetConstructor(Flags, null, ctorArgTypes, null);

            return ctor != null ? ctor.Invoke(ctorArgs).AsDynamic() : null;
        }
    }
}